/**
 * @author: Tyler Rhodes
 * @version: 0.1
 *
 * Created: 05/13/2012
 */

package j2600;

import java.util.Arrays;
import org.apache.log4j.*;

/** The Memory class of the atari 2600 */
public class Memory {
  /** Logger class used for logging purposes */
  public static final Logger logger = Logger.getLogger(Memory.class);

  /** Stack pointer */
  private int sp;
  /** memory */
  private char[] memory;
  /** Last used address, used for calculating page boundary crossing */
  private int last_used_address;

  /**
   * Constructor for the Memory class
   */
  public Memory() {
    logger.info("Creating Memory");
    memory = new char[0xFFFF];

    logger.info("Setting default values");

    Arrays.fill(memory, (char)0);
    sp = 0;
  }

  /**
   * Push a byte onto memory's stack.
   * @param value Value to be pushed onto the stack. Note: only the lowest byte is pushed.
   */
  public void stackPushByte(short value) {
    memory[sp] = (char)(value & 0xFF);
    sp++;
  }

  /**
   * Push a short onto memory's stack.
   * @param value Short to push onto stack. Note: only lowest 2 bytes pushed
   */
  public void stackPushShort(int value) {
    stackPushByte((short) ((value >> 8) & 0xFF));
    stackPushByte((short) (value & 0xFF));
  }

  /**
   * Pop a value off of the stack.
   * @return top value of stack.
   */
  public short stackPopByte() {
    sp--;
    return (short)memory[sp];
  }

  /**
   * Pop a short off of the stack.
   * @return top short on stack.
   */
  public int stackPopShort() {
    short low = stackPopByte();
    short hi = stackPopByte();
    return (low | (hi << 8));
  }

  /**
   * Read a single value at the given address.
   * @param address Location of byte.
   * @return value at requested address
   */
  public short readByte(int address) {
    checkMemoryBounds(address);
    last_used_address = address;
    return (short)memory[address];
  }

  /**
   * Read a short from the given address
   * @param address Location of short.
   * @return short located at requested address
   */
  public int readShort(int address) {
    short low = readByte(address);
    short hi = readByte(address + 1);
    return (low | (hi << 8));
  }

  /**
   * Read a single value at the given zero-page address.
   * @param address Location of byte.
   * @return value at requested address
   */
  public short readByte(short address) {
    checkZeroPageBounds(address);
    last_used_address = address;
    return (short)memory[address];
  }

  /**
   * Read a short from the given zero-page address
   * @param address Location of short.
   * @return short located at requested address
   */
  public int readShort(short address) {
    short low = readByte(address);
    short hi = readByte(address + 1);
    return (low | (hi << 8));
  }
  /**
   * Write a single byte at the given address
   * @param value Value to write.
   * @param address Location to write at.
   */
  public void writeByte(short value, int address) {
    checkMemoryBounds(address);
    last_used_address = address;
    memory[address] = (char)(value & 0xFF);
  }

  /**
   * Write a short at a certain address.
   * @param value value to be written.
   * @param address location to be written to.
   */
  public void writeShort(int value, int address) {
    short low = (short) (value & 0xFF);
    short hi = (short) ((value >> 8) & 0xFF);
    writeByte(low, address);
    writeByte(hi, address + 1);
  }
  /**
   * Write a single byte at the given zero-page address
   * @param value Value to write.
   * @param address Location to write at.
   */
  public void writeByte(short value, short address) {
    checkZeroPageBounds(address);
    last_used_address = address;
    memory[address] = (char)(value & 0xFF);
  }

  /**
   * Write a short at a certain zero-page address.
   * @param value value to be written.
   * @param address location to be written to.
   */
  public void writeShort(int value, short address) {
    short low = (short) (value & 0xFF);
    short hi = (short) ((value >> 8) & 0xFF);
    writeByte(low, address);
    writeByte(hi, address + 1);
  }

  public void writeBackByte(short value) {
    writeByte(value, last_used_address);
  }
  public void writeBackShort(int value) {
    writeShort(value, last_used_address);
  }

  /**
   * Determine if a page boundary has been crossed, by comparing
   * the given address, and the previously used address.
   * @param a1 First address to compare.
   * @param a2 Second address to compare.
   * @return True if page boundary was crossed.
   */
  private boolean pageBoundaryCrossed(int a1, int a2) {
    int a1_page = a1 >> 8;
    int a2_page = a2 >> 8;

    return a1_page != a2_page;
  }

  /**
   * Number of cycles added for a page-boundary crossing.
   * @param address Address to compare against.
   * @return 1 if page boundary was crossed, 0 otherwise.
   */
  public int pageBoundaryCycles(int address) {
    return pageBoundaryCrossed(address, last_used_address) ? 1 : 0;
  }
  public int pageBoundaryCycles(int a1, int a2) {
    return pageBoundaryCrossed(a1, a2) ? 1 : 0;
  }

  /**
   * See if the address is in bounds.
   */
  private void checkMemoryBounds(int address) {
    if (address >= memory.length || address < 0) {
      logger.error("Invalid memory address: " + address);
    }
  }

  /**
   * See if address is on the zero-th page
   */
  private void checkZeroPageBounds(short address) {
    if (address >= 0x100 || address < 0) {
      logger.error("Invalid memory address: " + address);
    }
  }

  // -------- Indexing Helpers --------

  /**
   * Grab the byte at the given address.
   */
  public short absolute(int address) {
    return readByte(address);
  }

  /**
   * Grab the byte at the zero-page address given.
   */
  public short zeroPageAbsolute(short address) {
    return readByte(address);
  }

  /**
   * Grab the byte at the given address and index.
   */
  public short absoluteIndexed(int address, short index) {
    int actual_address = address + index;
    return readByte(actual_address);
  }

  /**
   * Grab the byte at the given zero-page address and index.
   */
  public short zeroPageIndexed(short address, short index) {
    short actual_address = (short)(address + index);
    return readByte(actual_address);
  }

  /**
   * Grab the byte using pre-indexed indirect addressing.
   */
  public short preIndexedIndirect(short address, short index) {
    int indirect_address = address + index;
    int actual_address = readShort(indirect_address);
    return readByte(actual_address);
  }

  /**
   * Grab the byte using post-indexed indirect addressing.
   */
  public short postIndexedIndirect(short address, short index) {
    int indirect_address = address;
    int actual_address = readShort(indirect_address) + index;
    return readByte(actual_address);
  }

}

/*
 * address Variables:
 * c-basic-offset: 2
 * End:
 */
